/*-------------------------------------------------------------------------
   Copyright (C) 2011 One Laptop per Child

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   In other words, you are welcome to use, share and improve this program.
   You are forbidden to forbid anyone else to use, share and improve
   what you give them.   Help stamp out software-hoarding!

   As a special exception, you may use this file as part of a free software
   library for the XO of the One Laptop per Child project without restriction.
   Specifically, if other files instantiate
   templates or use macros or inline functions from this file, or you compile
   this file and link it with other files to produce an executable, this
   file does not by itself cause the resulting executable to be covered by
   the GNU General Public License.  This exception does not however
   invalidate any other reasons why the executable file might be covered by
   the GNU General Public License.
-------------------------------------------------------------------------*/

#ifdef NO_PS2

#include <stdio.h>
#include "uart.h"
#include "cc_compat.h"
#include "power.h"
#include "io.h"
#include "io3731.h"

static bit kbd_watch_is_input;  // are we configured as input?
static unsigned char recent_suspend;


/* this is called any time we need to put the clock lines into
 * input mode:  i.e., at boot time, and when the host indicates
 * it's ready to receive input that we may be flow controlling.
 */
void kbd_watch_set_input(void)
{
	if (kbd_watch_is_input)
		return;

	IO_INPUT_ONLY(GPIO_EC_TPD_CLK);
	IO_INPUT_ONLY(GPIO_EC_KBD_CLK);

	// the jumpers to connect the EC to the kbd and tpad clock
	// lines will only be populated on boards 1c2 and higher.
	// but we'd like just installing the jumpers on older boards
	// to make keyboard resume work properly.  so mostly we don't
	// check board_rev.  here, though, we don't need (or want) pullups
	// when the jumpers are in place, but when they're not, the
	// inputs will float, and we don't want an interrupt storm.
	if (board_rev < 0xc2)
	{
		IO_PULLUP_ENABLE(GPIO_EC_TPD_CLK);
		IO_PULLUP_ENABLE(GPIO_EC_KBD_CLK);
	}

	kbd_watch_is_input = 1;
	// puts("kbd_watch_set_input");
}

void kbd_watch_init(void)
{
	// the data lines will always be input-only
	IO_INPUT_ONLY(GPIO_EC_TPD_DAT);	// Touchpad
	IO_INPUT_ONLY(GPIO_EC_KBD_DAT);	// Keyboard

	kbd_watch_set_input();

	// enable a SOC_SLEEP interrupt, so we can enable
	// our own interrupts only when the host is sleeping
	IO_WAKEUP_POLARITY_HIGH(GPIO_SOC_SLEEP);
	IO_WAKEUP_EDGE(GPIO_SOC_SLEEP);
	IO_WAKEUP_PENDING_CLEAR(GPIO_SOC_SLEEP);
	IO_WAKEUP_ENABLE(GPIO_SOC_SLEEP);

	P3IE1 = 1;

	// puts("kbd_watch_init");

}

void kbd_watch_enable_ints(void)
{
	// putchar('E');
	// ack any pending ints
	IO_WAKEUP_PENDING_CLEAR(GPIO_EC_TPD_CLK);
	IO_WAKEUP_PENDING_CLEAR(GPIO_EC_KBD_CLK);

	IO_WAKEUP_POLARITY_LOW(GPIO_EC_TPD_CLK);
	IO_WAKEUP_POLARITY_LOW(GPIO_EC_KBD_CLK);

	IO_WAKEUP_EDGE(GPIO_EC_TPD_CLK);
	IO_WAKEUP_EDGE(GPIO_EC_KBD_CLK);

	IO_WAKEUP_ENABLE(GPIO_EC_TPD_CLK);
	IO_WAKEUP_ENABLE(GPIO_EC_KBD_CLK);

	P3IE4 = 1;
}


void intsrv_gpio00_0f(void) interrupt 25
{
	if (IO_WAKEUP_PENDING(GPIO_SOC_SLEEP))
	{
		IO_WAKEUP_PENDING_CLEAR(GPIO_SOC_SLEEP);
		if (host_in_suspend())
		{
			kbd_watch_enable_ints();
		}
	}
	// putchar('S');
}


/*
 * we use the 'recent_suspend' counter to effectively
 * stretch the duration of SOC_SLEEP.  SOC_SLEEP will
 * go away almost simultaneously with the CLK falling
 * edge (since that wakes the SP up).
 */
void kbd_watch_process(void)
{
	if (host_in_suspend())
	{
		recent_suspend = 3;
	}
	else
	{
		//if (recent_suspend == 1)
		//	putchar('R');
		if (recent_suspend)
			recent_suspend--;
	}
}

void intsrv_gpio30_38(void) interrupt 28
{
	if (IO_WAKEUP_PENDING(GPIO_EC_TPD_CLK) ||
	IO_WAKEUP_PENDING(GPIO_EC_KBD_CLK))
	{
		IO_WAKEUP_PENDING_CLEAR(GPIO_EC_TPD_CLK);
		IO_WAKEUP_PENDING_CLEAR(GPIO_EC_KBD_CLK);

		if (recent_suspend || host_in_suspend())
		{
			IO_WAKEUP_DISABLE(GPIO_EC_TPD_CLK);
			IO_WAKEUP_DISABLE(GPIO_EC_KBD_CLK);

			// we got a falling clock edge.  assert flow control.
			IO_OUTPUT_ENABLE(GPIO_EC_TPD_CLK);
			IO_OUTPUT_ENABLE(GPIO_EC_KBD_CLK);

			IO_PULLUP_DISABLE(GPIO_EC_TPD_CLK);
			IO_PULLUP_DISABLE(GPIO_EC_KBD_CLK);

			IO_OUTPUT_LOW(GPIO_EC_TPD_CLK);
			IO_OUTPUT_LOW(GPIO_EC_KBD_CLK);

			kbd_watch_is_input = 0;
			// putchar('K');
		}
	}
	// putchar('2');
}

#endif
